/*
@(#)SecurityHolder   2024-11-07
 
Copyright (c) 2011-2024 杭州湖畔网络技术有限公司 
保留所有权利 
本软件为杭州湖畔网络技术有限公司所有及包含机密信息，须遵守其相关许可证条款进行使用。
Copyright (c) 2011-2024 HUPUN Network Technology CO.,LTD.
All rights reserved.
This software is the confidential and proprietary information of HUPUN
Network Technology CO.,LTD("Confidential Information").  You shall not
disclose such Confidential Information and shall use it only in
accordance with the terms of the license agreement you entered into with HUPUN.
Website：http://www.hupun.com
*/
package org.obp.upms.context;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.obp.upms.config.UpmsConfig;
import org.obp.upms.domain.response.ResourceTreeVO;
import org.obp.upms.model.WorkMenuDO;
import org.obp.upms.model.WorkRoleDO;
import org.obp.upms.model.WorkRoleMenuDO;
import org.obp.upms.model.WorkRoleResourceDO;
import org.obp.upms.service.WorkMenuService;
import org.obp.upms.service.WorkRoleMenuService;
import org.obp.upms.service.WorkRoleResourceService;
import org.obp.upms.service.WorkRoleService;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author: Jackey 2024/11/7
 */
@Slf4j
@Component
public class SecurityHolder {

    @Autowired
    private UpmsConfig upmsConfig;
    @Autowired
    private WorkRoleService workRoleService;
    @Autowired
    private WorkMenuService workMenuService;
    @Autowired
    private WorkRoleMenuService workRoleMenuService;
    @Autowired
    private WorkRoleResourceService workRoleResourceService;

    @Resource(name = "redisJsonTemplate")
    private RedisTemplate<String, ResourceRoleMeta> redisJsonTemplate;

    public static final String RESOURCE_ROLE_META_PREFIX = "RESOURCE_ROLE_META:";

    @PostConstruct
    public void initResourceRoleMeta() {

        if (!upmsConfig.isInitResourceMeta()) return;

        log.info("====== 初始化权限数据缓存开始 ======");

        this.initAdminResourceMeta();

        List<WorkRoleResourceDO> relations = workRoleResourceService.list();

        this.initResourceRoleMetaCache(relations);

        log.info("====== 初始化权限数据缓存结束 ======");
    }

    private void initAdminResourceMeta() {

        WorkRoleDO workRoleDO = new WorkRoleDO();
        workRoleDO.setId(WorkRoleDO.ADMIN_ROLE_ID);
        workRoleDO.setRoleName(WorkRoleDO.ADMIN_ROLE_NAME);
        workRoleService.saveOrUpdate(workRoleDO);

        List<WorkMenuDO> menuDOS = workMenuService.list();
        for (WorkMenuDO menuDO : menuDOS) {
            WorkRoleMenuDO workRoleMenuDO = new WorkRoleMenuDO();
            workRoleMenuDO.setRoleId(WorkRoleDO.ADMIN_ROLE_ID);
            workRoleMenuDO.setMenuId(menuDO.getId());
            workRoleMenuService.saveOrUpdate(workRoleMenuDO, new QueryWrapper<WorkRoleMenuDO>().lambda()
                    .eq(WorkRoleMenuDO::getRoleId, WorkRoleDO.ADMIN_ROLE_ID)
                    .eq(WorkRoleMenuDO::getMenuId, menuDO.getId()));
        }

        List<ResourceTreeVO> resourceTreeVOS = getResourceTree();
        for (ResourceTreeVO resourceTreeVO : resourceTreeVOS) {
            for (ResourceTreeVO.ResourceTreeItemVO resourceTreeItemVO : resourceTreeVO.getChildren()) {
                WorkRoleResourceDO workRoleResourceDO = new WorkRoleResourceDO();
                workRoleResourceDO.setRoleId(WorkRoleDO.ADMIN_ROLE_ID);
                workRoleResourceDO.setUrlMethod(resourceTreeItemVO.getUrlMethod());
                workRoleResourceDO.setUrlPath(resourceTreeItemVO.getUrlPath());
                workRoleResourceService.saveOrUpdate(workRoleResourceDO, new QueryWrapper<WorkRoleResourceDO>().lambda()
                        .eq(WorkRoleResourceDO::getRoleId, WorkRoleDO.ADMIN_ROLE_ID)
                        .eq(WorkRoleResourceDO::getUrlMethod, resourceTreeItemVO.getUrlMethod())
                        .eq(WorkRoleResourceDO::getUrlPath, resourceTreeItemVO.getUrlPath()));
            }
        }
    }

    private void initResourceRoleMetaCache(List<WorkRoleResourceDO> relations) {
        Map<String, List<WorkRoleResourceDO>> relationMap = relations.stream()
                .collect(Collectors.groupingBy(r -> ResourceRoleMeta.buildUrlKey(r.getUrlMethod(), r.getUrlPath())));
        for (String urlKey : relationMap.keySet()) {
            List<String> roleIds = relationMap.get(urlKey).stream()
                    .map(WorkRoleResourceDO::getRoleId).collect(Collectors.toList());
            ResourceRoleMeta resourceRoleMeta = new ResourceRoleMeta(urlKey, roleIds);
            BoundValueOperations<String, ResourceRoleMeta> operations = redisJsonTemplate.boundValueOps(RESOURCE_ROLE_META_PREFIX + urlKey);
            operations.set(resourceRoleMeta);
            log.info(JSONObject.toJSONString(resourceRoleMeta));
        }
    }

    @Data
    @AllArgsConstructor
    public static class ResourceRoleMeta implements Serializable {

        private static final long serialVersionUID = 284603827570758914L;

        private String urlKey;

        private List<String> roleIds;

        public static String buildUrlKey(String urlMethod, String urlPath) {
            return urlMethod + "-" + urlPath;
        }

        public ResourceRoleMeta() {
        }

    }

    /**
     * 动态解析接口数据
     *
     * @return
     */
    public static List<ResourceTreeVO> getResourceTree() {

        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(RestController.class));
        Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents("org.obp");

        List<ResourceTreeVO> resourceTreeVOS = new ArrayList<>();

        for (BeanDefinition beanDefinition : beanDefinitions) {
            Class<?> clazz;
            try {
                clazz = Class.forName(beanDefinition.getBeanClassName());
            } catch (ClassNotFoundException ex) {
                log.error(ex.getMessage(), ex);
                continue;
            }
            Api api = clazz.getAnnotation(Api.class);
            if (api == null || api.hidden()) continue;
            String apiTag = api.tags()[0];
            String apiPrefix = clazz.getAnnotation(RequestMapping.class).value()[0];

            ResourceTreeVO resourceTreeVO = new ResourceTreeVO();
            resourceTreeVO.setName(apiTag);

            AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
            AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
            Set<MethodMetadata> mappingMethodMetadata = annotationMetadata.getAnnotatedMethods(RequestMapping.class.getName());
            for (MethodMetadata methodMetadata : mappingMethodMetadata) {
                MergedAnnotations mergedAnnotations = methodMetadata.getAnnotations();

                MergedAnnotation<ApiOperation> apiOperationM = mergedAnnotations.get(ApiOperation.class);
                if (!apiOperationM.isPresent() || apiOperationM.getBoolean("hidden")) continue;
                String apiOperation = apiOperationM.getString("value");
                String apiPath = mergedAnnotations.get(RequestMapping.class).getStringArray("value")[0];
                String apiMethod = mergedAnnotations.get(RequestMapping.class).getEnumArray("method", RequestMethod.class)[0].name();

                ResourceTreeVO.ResourceTreeItemVO resourceTreeItemVO = new ResourceTreeVO.ResourceTreeItemVO();
                resourceTreeItemVO.setName(apiOperation);
                resourceTreeItemVO.setUrlPath(apiPrefix + apiPath);
                resourceTreeItemVO.setUrlMethod(apiMethod);
                resourceTreeVO.getChildren().add(resourceTreeItemVO);
            }

            resourceTreeVOS.add(resourceTreeVO);
        }

        return resourceTreeVOS;
    }

    public ResourceRoleMeta getResourceRoleMetaByUrl(String urlMethod, String urlPath) {
        String urlKey = ResourceRoleMeta.buildUrlKey(urlMethod, urlPath);
        return redisJsonTemplate.boundValueOps(RESOURCE_ROLE_META_PREFIX + urlKey).get();
    }

    public boolean refreshResourceRoleMeta(String urlMethod, String urlPath) {
        List<WorkRoleResourceDO> relations = workRoleResourceService.lambdaQuery()
                .eq(WorkRoleResourceDO::getUrlMethod, urlMethod)
                .eq(WorkRoleResourceDO::getUrlPath, urlPath).list();
        if (CollectionUtils.isEmpty(relations)) return false;
        this.initResourceRoleMetaCache(relations);
        return true;
    }

}